/* -------------------------------------------------------------------------
 *
 * pgstat_function.c
 *	  函数统计的实现。
 *
 * 本文件包含函数统计的实现。它与pgstat.c分开，以强制统计访问/存储实现与各类型统计的细节之间的界限。
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/pgstat_function.c
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "fmgr.h"
#include "utils/inval.h"
#include "utils/pgstat_internal.h"
#include "utils/syscache.h"


/* ----------
 * GUC 参数
 * ----------
 */
int			pgstat_track_functions = TRACK_FUNC_OFF;


/*
 * 在当前后端中已经向函数收取的总时间。
 * 我们利用这个来帮助区分“自我”和“其他”的时间收费。
 * （我们假设这个初始化为零。）
 */
static instr_time total_func_time;


/*
 * 如果事务中止，则确保丢弃统计信息。
 */
void pgstat_create_function(Oid fc_proid)
{
	pgstat_create_transactional(PGSTAT_KIND_FUNCTION,
								MyDatabaseId,
								fc_proid);
}

/*
 * 如果事务提交，则确保丢弃统计信息。
 *
 * 注意：这只有在pgstat_init_function_usage()做了一些
 * 额外工作时才可靠。如果其他地方开始发出函数统计信息，
 * 它们很可能也需要类似的逻辑。
 */
void pgstat_drop_function(Oid fc_proid)
{
	pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
							  MyDatabaseId,
							  fc_proid);
}

/*
 * 初始化函数调用使用数据。
 * 在调用函数之前由执行器调用。
 */
void pgstat_init_function_usage(FunctionCallInfo fc_fcinfo,
						   PgStat_FunctionCallUsage *fc_fcu)
{
	PgStat_EntryRef *fc_entry_ref;
	PgStat_BackendFunctionEntry *fc_pending;
	bool		fc_created_entry;

	if (pgstat_track_functions <= fc_fcinfo->flinfo->fn_stats)
	{
		/* 不想要统计信息 */
		fc_fcu->fs = NULL;
		return;
	}

	fc_entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION,
										  MyDatabaseId,
										  fc_fcinfo->flinfo->fn_oid,
										  &fc_created_entry);

	/*
	 * 如果没有共享条目已经存在，检查该函数是否已经
	 * 被并发删除。这可能直到这里才被注意到，因为
	 * 执行一个只是调用函数的语句，不会触发
	 * 缓存失效处理。我们关心这种情况的原因是，
	 * 否则我们可能会为一个已经被删除的函数创建一个新的
	 * 统计条目（对于关系等，这是不可能的，因为发出统计信息
	 * 需要已经获取该关系的锁）。
	 *
	 * 根据track_functions的启用/禁用状态有行为差异，
	 * 这有点丑陋。但是考虑到已经存在的行为差异，
	 * 这似乎是可以接受的，这取决于函数是否为缓存等。
	 *
	 * 为了正确性，设置->dropped为true就足够了。然而，
	 * 被接受的失效通常会导致PL代码中的“低级”失败，
	 * 错误消息中带有OID。这使得测试变得更困难...
	 */
	if (fc_created_entry)
	{
		AcceptInvalidationMessages();
		if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fc_fcinfo->flinfo->fn_oid)))
		{
			pgstat_drop_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId,
							  fc_fcinfo->flinfo->fn_oid);
			ereport(ERROR, errcode(ERRCODE_UNDEFINED_FUNCTION),
					errmsg("function call to dropped function"));
		}
	}

	fc_pending = fc_entry_ref->pending;

	fc_fcu->fs = &fc_pending->f_counts;

	/* 保存此函数的统计信息，稍后用于补偿递归 */
	fc_fcu->save_f_total_time = fc_pending->f_counts.f_total_time;

	/* 保存当前后端总时间 */
	fc_fcu->save_total = total_func_time;

	/* 获取函数开始时的时钟时间 */
	INSTR_TIME_SET_CURRENT(fc_fcu->f_start);
}

/*
 * 计算函数调用使用情况并更新统计计数器。
 * 在调用函数之后由执行器调用。
 *
 * 对于在按调用值模式下运行的集返回函数，
 * 我们将看到多个pgstat_init_function_usage/pgstat_end_function_usage
 * 调用，对于用户认为的单个函数调用。最后一次调用时
 * finalize标志应为TRUE。
 */
void pgstat_end_function_usage(PgStat_FunctionCallUsage *fc_fcu, bool fc_finalize)
{
	PgStat_FunctionCounts *fc_fs = fc_fcu->fs;
	instr_time	fc_f_total;
	instr_time	fc_f_others;
	instr_time	fc_f_self;

	/* 不想要统计信息？ */
	if (fc_fs == NULL)
		return;

	/* 此函数调用的总经过时间 */
	INSTR_TIME_SET_CURRENT(fc_f_total);
	INSTR_TIME_SUBTRACT(fc_f_total, fc_fcu->f_start);

	/* 自我使用：已用时间减去已收取的其他调用时间 */
	fc_f_others = total_func_time;
	INSTR_TIME_SUBTRACT(fc_f_others, fc_fcu->save_total);
	fc_f_self = fc_f_total;
	INSTR_TIME_SUBTRACT(fc_f_self, fc_f_others);

	/* 更新后端总时间 */
	INSTR_TIME_ADD(total_func_time, fc_f_self);

	/*
	 * 计算新的f_total_time，作为总经过时间加上
	 * f_total_time的调用前值。这是必要的，以避免
	 * 自身的递归调用所需时间的双重计数。（我们不
	 * 需要类似的解决办法来处理自我时间，因为那已经排除了
	 * 任何递归调用。）
	 */
	INSTR_TIME_ADD(fc_f_total, fc_fcu->save_f_total_time);

	/* 更新函数统计表中的计数器 */
	if (fc_finalize)
		fc_fs->f_numcalls++;
	fc_fs->f_total_time = fc_f_total;
	INSTR_TIME_ADD(fc_fs->f_self_time, fc_f_self);
}

/*
 * 刷新条目的待处理统计信息
 *
 * 如果 nowait 为真，如果无法立即获取锁，则该函数返回 false，
 * 否则返回 true。
 */
bool pgstat_function_flush_cb(PgStat_EntryRef *fc_entry_ref, bool fc_nowait)
{
	PgStat_BackendFunctionEntry *fc_localent;
	PgStatShared_Function *fc_shfuncent;

	fc_localent = (PgStat_BackendFunctionEntry *) fc_entry_ref->pending;
	fc_shfuncent = (PgStatShared_Function *) fc_entry_ref->shared_stats;

	/* localent 始终具有非零内容 */

	if (!pgstat_lock_entry(fc_entry_ref, fc_nowait))
		return false;

	fc_shfuncent->stats.f_numcalls += fc_localent->f_counts.f_numcalls;
	fc_shfuncent->stats.f_total_time +=
		INSTR_TIME_GET_MICROSEC(fc_localent->f_counts.f_total_time);
	fc_shfuncent->stats.f_self_time +=
		INSTR_TIME_GET_MICROSEC(fc_localent->f_counts.f_self_time);

	pgstat_unlock_entry(fc_entry_ref);

	return true;
}

/*
 * 查找指定函数的任何现有 PgStat_BackendFunctionEntry 条目
 *
 * 如果没有条目，则返回 NULL，不创建新条目
 */
PgStat_BackendFunctionEntry *
find_funcstat_entry(Oid fc_func_id)
{
	PgStat_EntryRef *fc_entry_ref;

	fc_entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, fc_func_id);

	if (fc_entry_ref)
		return fc_entry_ref->pending;
	return NULL;
}

/*
 * 用于 SQL 可调用 pgstat* 函数的支持函数。返回
 * 一个函数收集的统计信息或 NULL。
 */
PgStat_StatFuncEntry *
pgstat_fetch_stat_funcentry(Oid fc_func_id)
{
	return (PgStat_StatFuncEntry *)
		pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, fc_func_id);
}
